/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.worker.manager;

import org.openislands.oi.constant.*;
import org.openislands.oi.dao.entity.DefaultOperatorDO;
import org.openislands.oi.dao.entity.OperatorDO;
import org.openislands.oi.error.LogicException;
import org.openislands.oi.manager.CommonOperatorManager;
import org.openislands.oi.operator.OperatorDefinitionConfig;
import org.openislands.oi.pojo.dto.OperatorDTO;
import org.openislands.oi.util.DirectoryUtils;
import org.openislands.oi.util.FileUtils;
import org.openislands.oi.util.StringUtils;
import org.openislands.oi.util.ValueCopyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;

import javax.annotation.Resource;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service
public class OperatorManager {
    private static final Logger logger = LoggerFactory.getLogger(OperatorManager.class);
    @Resource
    private CommonOperatorManager commonOperatorManager;

    public Boolean scan(){
        logger.info("start to scan operators");
        Map<OperatorType, Map<AlgorithmType, OperatorDTO>> defaultOperators = this.scanLocalOperators();
        this.initDefaultOperator(defaultOperators);
        logger.info("finish scan operators");
        return true;
    }

    public Map<OperatorType, Map<AlgorithmType, OperatorDTO>> scanLocalOperators(){
        Path rootPath = DirectoryUtils.getLocalOperatorDirectory();
        logger.info("start to scan operators from {} local path", rootPath);
        File[] vendors = rootPath.toFile().listFiles();
        assert vendors != null;
        Map<OperatorType, Map<AlgorithmType, OperatorDTO>> defaultOperators = new HashMap<>();
        for (OperatorType st: OperatorType.values()){
            defaultOperators.put(st, new HashMap<>());
        }
        for (File vendor: vendors){
            logger.info("start to scan {}", vendor);
            if (!vendor.isDirectory()){
                logger.info("{} is not vendor directory, pass", vendor);
                continue;
            }
            File[] versions = vendor.listFiles();
            if (versions != null && versions.length > 0){
                logger.info("vendor {} has {} versions operator", vendor.getName(), versions.length);
                for (File version: versions){
                    File[] packages = version.listFiles();
                    if (packages != null && packages.length > 0){
                        logger.info("vendor {} {} version has {} packaged type operator", vendor.getName(), version, packages.length);
                        for (File package_path: packages){
                            try {
                                OperatorPackagedType packagedType = OperatorPackagedType.valueOf(package_path.getName().toUpperCase());
                                initOperator(package_path, packagedType, defaultOperators);
                            }catch (IllegalArgumentException e){
                                logger.warn("not support {} packaged type", package_path.getName());
                            }catch (Exception e){
                                logger.error("scan operator error: ", e);
                            }
                        }
                    }else {
                        logger.info("vendor {} {} version has no operator package", vendor.getName(), version);
                    }
                }
            }else {
                logger.info("vendor {} has no versions operator, break", vendor.getName());
            }
        }
        return defaultOperators;
    }

    public void initOperator(File packagePath, OperatorPackagedType packagedType, Map<OperatorType, Map<AlgorithmType, OperatorDTO>> defaultOperators){
        logger.info("start to scan operators from local path {}", packagePath);
        Yaml yaml = new Yaml(new Constructor(OperatorDefinitionConfig.class));
        String configContent = FileUtils.readSmallFile(Paths.get(packagePath.getAbsolutePath(), Dict.OPERATOR_DEFINE_FILE));
        OperatorDefinitionConfig operatorDefinitionConfig = yaml.load(configContent);
        final OperatorDO operatorDO = new OperatorDO();
        String vendor = operatorDefinitionConfig.getVendor();
        String version = operatorDefinitionConfig.getVersion();
        operatorDO.setVendorId(vendor);
        operatorDO.setVersion(version);
        operatorDO.setDriver(operatorDefinitionConfig.getDriver());
        operatorDO.setPackagedType(packagedType);
        operatorDO.setSourceUri("file://" + packagePath.getAbsolutePath());
        Map<String, String> env = operatorDefinitionConfig.getEnv();
        if ((env.containsKey(Dict.PYTHONPATH) || env.containsKey(Dict.PYTHONPATH.toLowerCase())) && StringUtils.isEmpty(env.getOrDefault(Dict.PYTHONPATH, env.get(Dict.PYTHONPATH.toLowerCase())))){
            String defaultPythonPath = DirectoryUtils.getOperatorDefaultPythonpath(operatorDO.getSourceUri());
            logger.info("set default pythonpath {}", defaultPythonPath);
            env.put(Dict.PYTHONPATH, defaultPythonPath);
        }
        if ((env.containsKey(Dict.JAVA_HOME) || env.containsKey(Dict.JAVA_HOME.toLowerCase())) && StringUtils.isEmpty(env.getOrDefault(Dict.JAVA_HOME, env.get(Dict.JAVA_HOME.toLowerCase())))){
            env.put(Dict.JAVA_HOME, System.getenv(Dict.JAVA_HOME));
            logger.info("using worker java home for operator {}", System.getenv(Dict.JAVA_HOME));
        }
        operatorDO.setEnv(env);
        operatorDefinitionConfig.getOperator().forEach((st, ats) -> {
            OperatorType type = OperatorType.valueOf(st.toUpperCase());
            ats.forEach((at, entrypoint) -> {
                AlgorithmType algorithmType = AlgorithmType.valueOf(at.toUpperCase());
                operatorDO.setType(type);
                operatorDO.setAlgorithm(algorithmType);
                operatorDO.setEntrypoint(entrypoint);
                operatorDO.setCreateTime(new Date());
                //todo: Specifies the fields to update
                int rows = commonOperatorManager.mysqlInsertOrUpdate(operatorDO);
                if(rows > 0){
                    logger.info("get {} {} operator from {} {}", type, algorithmType, vendor, version);
                    if (!defaultOperators.get(type).containsKey(algorithmType)){
                        defaultOperators.get(type).put(algorithmType, ValueCopyUtils.copyProperties(operatorDO, new OperatorDTO()));
                    }else {
                        defaultOperators.get(type).put(algorithmType, null);
                    }
                }
            });
        });
        logger.info("finish scan operators from local path {}", packagePath);
    }

    public void initDefaultOperator(Map<OperatorType, Map<AlgorithmType, OperatorDTO>> defaultOperators){
        logger.info("start to scan default operators");
        defaultOperators.forEach((st, ats) -> {
            ats.forEach((at, op) -> {
                try {
                    if (op != null){
                        DefaultOperatorDO defaultOperatorDO = ValueCopyUtils.copyProperties(op, new DefaultOperatorDO());
                        int rows = commonOperatorManager.mysqlInsertOrUpdate(defaultOperatorDO);
                        if (rows > 0){
                            logger.info("set {} {} as {} {} default operator", op.getVendorId(), op.getVersion(), op.getType(), op.getAlgorithm());
                        }
                    }
                }catch (Exception e){
                    logger.error("insert default operator to database error", e);
                    throw new LogicException(MessageCodeEnum.SYSTEM_ERROR, "insert default operator error");
                }
            });
        });
        logger.info("finish scan default operators");
    }
}
